package cogpt

import (
	"cogpt/internal/cache"
	myErr "cogpt/internal/cogpterror"
	"cogpt/internal/config"
	"cogpt/internal/log"
	"cogpt/internal/proxy"
	"cogpt/internal/util"
	"encoding/json"
	"io"
	"net/http"
	"strconv"
	"time"
)

const (
	COPILOTCHAT_VERSION = "0.12.2023120701"
	VS_CODE_VERSION     = "1.85.2"
)

// @description Get Copilot Token
//
// @param
//   - app_token string "app token"
//
// @return string "copilot token"
func GetCopilotToken(app_token string) (string, myErr.Error) {
	item, err := cache.CacheInstance.Get(app_token)
	// cache exists and not expired
	if err == nil && item.ExpiresAt > time.Now().Unix() {
		log.Logger.Debug().Str("type", "GetCopilotToken").Msg("Get copilot app token from cache")
		return item.C_token, nil
	} else {
		// get a new token
		log.Logger.Debug().Str("type", "GetCopilotToken").Msg("Start to get copilot app token from api")
		url := "https://api.github.com/copilot_internal/v2/token"

		var err error
		client, err := proxy.GenProxyClient(config.ConfigInstance.Proxy, 0)
		if err != nil {
			log.Logger.Error().Str("type", "GetCopilotToken").Msgf("Failed to create http client, error: %s", err.Error())
			return "", &myErr.CoErr{
				ErrCode: myErr.ERR_FAILED_TO_CREATE_HTTP_CLIENT,
				Msg:     err.Error(),
				Typ:     "GetCopilotToken",
			}
		}
		req, err := http.NewRequest("GET", url, nil)
		// fail to create http request
		if err != nil {
			log.Logger.Error().Str("type", "GetCopilotToken").Msgf("Failed to create http request, error: %s", err.Error())
			return "", &myErr.CoErr{
				ErrCode: myErr.ERR_FAILED_TO_CREATE_HTTP_CLIENT,
				Msg:     err.Error(),
				Typ:     "GetCopilotToken",
			}
		}
		// set headers
		req.Header.Set("Authorization", "Bearer "+app_token)
		// send request
		resp, err := client.Do(req)
		// fail to finish http request
		if err != nil {
			log.Logger.Error().Str("type", "GetCopilotToken").Msgf("Failed to finish http request, error: %s", err.Error())
			return "", &myErr.CoErr{
				ErrCode: myErr.ERR_FAILED_TO_FINISH_HTTP_REQUEST,
				Msg:     err.Error(),
				Typ:     "GetCopilotToken",
			}
		}
		defer resp.Body.Close()
		// read response body
		body, err := io.ReadAll(resp.Body)
		if err != nil {
			log.Logger.Error().Str("type", "GetCopilotToken").Msgf("Failed to read http response body, error: %s", err.Error())
			return "", &myErr.CoErr{
				ErrCode: myErr.ERR_FAILED_TO_READ_HTTP_RESPONSE_BODY,
				Msg:     err.Error(),
				Typ:     "GetCopilotToken",
			}
		}
		if resp.StatusCode != 200 {
			log.Logger.Debug().Str("type", "GetCopilotToken").Msgf("Failed to get copilot token, status code: %d, body: %s", resp.StatusCode, string(body))
			if resp.StatusCode == 403 {
				return "", &myErr.CoErr{
					ErrCode: myErr.ERR_NO_COPILOT_ACCESS,
					Msg: string(body) + "\n" +
						"Status Code: " + strconv.Itoa(resp.StatusCode),
					Typ: "GetCopilotToken",
				}
			}
			return "", &myErr.CoErr{
				ErrCode: myErr.ERR_FAILED_TO_GET_COPILOT_TOKEN,
				Msg: string(body) + "\n" +
					"Status Code: " + strconv.Itoa(resp.StatusCode),
				Typ: "GetCopilotToken",
			}
		}
		// parse response body
		var authorization Authorization
		if err := json.Unmarshal(body, &authorization); err != nil {
			log.Logger.Error().Str("type", "GetCopilotToken").Msgf("Failed to parse json, error: %s", err.Error())
			return "", &myErr.CoErr{
				ErrCode: myErr.ERR_FAILED_TO_PARSE_JSON,
				Msg:     err.Error(),
				Typ:     "GetCopilotToken",
			}
		}
		// save token to cache
		cache.CacheInstance.Set(app_token, &cache.Item{
			App_token: app_token,
			C_token:   authorization.Token,
			ExpiresAt: authorization.ExpiresAt,
		})
		log.Logger.Debug().Str("type", "GetCopilotToken").Msg("Got copilot app token from api")
		return authorization.Token, nil
	}
}

// @description Generate Headers
//
// @param
//   - cToken string "copilot token"
//   - machineID string "machine ID"
//   - sessionID string "session ID"
//   - stream bool "stream"
//
// @return map[string]string "headers"
func GenHeaders(cToken string, machineID string, sessionID string, stream bool) map[string]string {
	headers := make(map[string]string)
	headers["Authorization"] = "Bearer " + cToken
	headers["X-Request-Id"] = util.GenRequestID()
	headers["Vscode-Sessionid"] = sessionID
	headers["Vscode-Machineid"] = machineID
	headers["Editor-Version"] = "vscode/" + VS_CODE_VERSION
	headers["Editor-Plugin-Version"] = "copilot-chat/" + COPILOTCHAT_VERSION
	headers["Openai-Organization"] = "github-copilot"
	headers["Openai-Intent"] = "conversation-panel"
	if stream {
		headers["Content-Type"] = "text/event-stream; charset=utf-8"
	} else {
		headers["Content-Type"] = "application/json; charset=utf-8"
	}
	headers["User-Agent"] = "GitHubCopilotChat/" + COPILOTCHAT_VERSION
	headers["Accept"] = "*/*"
	headers["Accept-Encoding"] = "gzip,deflate,br"
	headers["Connection"] = "close"
	return headers
}

// @description Generate Model
//
// @param
//   - modelID string "model ID"
//
// @return map[string]any "model"
func GenModel(modelID string) map[string]any {
	return map[string]any{
		"id":       modelID,
		"object":   "model",
		"created":  1677610602,
		"owned_by": "openai",
		"permission": map[string]any{
			"id":                   "modelperm-" + util.GenRandomHex(12),
			"object":               "model_permission",
			"created":              1677610602,
			"allow_create_engine":  false,
			"allow_sampling":       true,
			"allow_logprobs":       true,
			"allow_search_indices": false,
			"allow_view":           true,
			"allow_fine_tuning":    false,
			"organization":         "*",
			"group":                nil,
			"is_blocking":          false,
		},
		"root":   modelID,
		"parent": nil,
	}
}
